Assembly Source on Text Files

Version 4.0 of the S-C Assembler II allows you to EXEC a source program, if it is on a DOS text file.  This is handy if you have created it with a different editor, or perhaps with a compiler.  But what if you want to go the other way?  What if you want to SAVE a source program on a text file, so that it can be used in another editor, or by another assembler?

There is no built-in command to allow it, so I have now written a separate program to do it.  The program loads at $0800 thru $093C, and does not borrow any code from the assembler.  It does use some routines in the Monitor ROMs, and the DOS I/O rehook routine.  If you BRUN the program, it will assume the pointers at $CA,CB and $4C,4D are bracketing a valid assembly source program, and try to list it on a text file.

The main body of the program is in lines 1190 thru 1630.  Lines 1200 and 1210 serve to un-hook the S-C Assembler II from the output.  They will also turn off your printer, if you had it on.  Lines 1220 and 1230 tell DOS that it should recognize commands printed after a control-D.  Lines 1240 and 1250 change the prompt symblol to a blank, so that the monitor input subroutine will not print a colon or some other character as the prompt when reading the file name.

Lines 1290-1360 request you to enter a file name, read it into the monitor buffer starting at $0200, and move it to a safe place at $0280.  It has to be moved, because when we print DOS commands later the area starting at $0200 will be written on by DOS.

Once the file name you have typed is safely stored at $0280 and following, lines 1410 thru 1490 will set up the file for writing.  This is done in five steps.  First, close all files.  Second, issue an OPEN-DELETE-OPEN sequence, with the file name (of course); this will make sure that we are writing on a fresh empty file.  Then the WRITE command is sent, and we are ready to roll.

Line 1530 calls a subroutine which lists your source program.  Since the file is OPEN and in WRITE mode, the listing goes into your text file.  If you have MON O mode set, you will also see the listing on your screen.  Note that it is not really necessary for me to use a subroutine at this point.  ASM.LIST is only called once, and it is not very long.  But I did it anyway, to keep the main body short enough to fit on a page (of paper), easy to understand, modular, structured, etc.

After the listing is completed, line 1570 will close the text file.  Lines 1610 and 1620 turn off the DOS run flag, so that DOS will not look for control-D commands.  And finally, line 1630 re-enters the S-C Assembler II through its soft entry point.

Lines 1670 thru 1780 are text strings, printed by the subroutine named PRINT.QUOTE.  Each string is written with the sign bit of every byte zero except for the last byte.  The sign bit of the last byte is 1, telling PRINT.QUOTE that it is finished.  For example, the first message is the word "CLOSE" and a carriage return.  The carriage return is entered in hex with the sign bit 1 as in $8D.  The second message is the word "OPEN", and the letter "N" is preceded by a minus sign in the .AS directive to indicate that the sign bit should be 1.

The PRINT.QUOTE subroutine is at lines 2140 thru 2200.  It expects the Y-register to contain the offset of the desired message from the beginning of all the messages at QTS.  It calls on PRINT.CHAR to actually send each character.

PRINT.CHAR, at lines 2020 thru 2100, calls on the monitor print character routine at $FDED.  This branches through DOS, and DOS writes the character on the text file.  PRINT.CHAR saves and restores the Y-register and A-register contents.  It also sets the sign bit on each character before printing it.  Upon exit, the status will reflect the value of the character printed.

Lines 1820-1980 issue a DOS command.  The Y-register points at one of the message strings in QTS.  Control-D is printed, followed by the command key word, a space, and file name you previously typed.  Since DOS does not allow slot and drive specifications on the WRITE command, and since it is sufficient to specify them only once, the subroutine chops them off after printing them once.  The logic for this is in lines 1910-1940:  after printing a comma, it is replaced with a carriage return.  The next time the name is printed, the carriage return will  be the end.

The subroutine which really controls the listing is in lines 2330-2450.  The first four instructions set up a zero-page pointer SRCP to point at the beginning of your source program.  Lines 2380-2420 compare the pointer with HIMEM to see if the listing is completed.  If you really had no source program, we would already be finished at this point.  If there is another line (or more), the subroutine named ASM.LIST.LINE is called to list the next lne.  The process is repeated until the last line has been printed onto your text file.

At this point it might be helpful to explain how source lines are stored in memory.  Each line begins with a single byte which contains the byte-count of the line.  Next are a byte-pair containing the line number of the line, in the usual backwards 6502 format.  The text of the line follows, and a final byte containing $00 ends the line.  No carriage return is stored.  Blanks are treated specially.  A single blank is stored as $81.  Two blanks in a row are replaced by one byte of value $82.  Any string of blanks up to 63 blanks is thus replaced by a single token of value $80 plus the blank count.  Longer strings of blanks will take more than one token.

For example, the source line

       1000 ABC    LDA SAM

is stored as:  0F  (total of 15 bytes in line image)
               E8 03   (line number 1000)
               41 42 43 84   ("ABC" and 4 blanks)
               4C 44 41 81   ("LDA" and 1 blank)
               53 41 4D      ("SAM")
               00      (end of line indicator)

The subroutine ASM.LIST.LINE at lines 2490-2610 prints one source line.  A subroutine named GNB ("get next byte") is called to skip over the length byte, and to pick up the line number.  PRINT.LINNUM is called to convert the line number to decimal and print it, with leading zeroes if necessary, as a four digit number.  The loop at lines 2570-2600 is seeded with a blank (because the blank between the line number and the label field is not actually stored in the source program), and the text of the line is printed.  The loop prints a character, and then calls NEXT.TOKEN to get the next one.  When the token returned equals $00, the line is finished.

GNB, lines 2630-2690, clears the queued blank count, picks up the character pointed at by SRCP, and increments SRCP.

NEXT.TOKEN, lines 2710-2820, tests the blank count.  If it is non-zero, the count is decremented and a blank ($20) character is returned.  If the count was zero, the next character is picked up from the line.  If this character is not a blank count token, it is returned and the pointer in SRCP is incremented.  If the character is a blank count token, it is saved, the SRCP pointer is incremented past the token, and then the count is decremented and a blank returned.

The PRINT.LINNUM routine, lines 2860-3170, is a revision of a routine used in the Integer BASIC ROMs.  I think it is commented well enough for you to follow.  The general idea is to divide by 1000 and print the quotient; divide the remainder by 100 and print the quotient; then by 10; and finally print the remainder.

Since several of you have asked me to provide the capability to list programs onto text files, you should be pleased with this program.  If you do not need it, then maybe it has shed some light on the internal structure of part of the assembler, or served as a tutorial in programming.
